Example Workflow
As the name might suggest, structures can be created and analyzed with fair ease, with each problem being started with
using SimpleStatics
my_setup = StaticSetup()
StaticSetup{Float64}(SimpleStatics.Vector2D{Float64}[], SimpleStatics.Vector2D{Float64}[], SimpleStatics.StaticConstraint[], StaticMaterial{Float64}[], Dict{SimpleStatics.UnorderedPair, Int32}(), SimpleStatics.UnorderedPair[])
creating an empty statics problem which can then be filled with members, joints, forces, etc.
Lets create a simple truss to analyze.
1. Add Joints
First, we need to add some joints. Joints act as terminals to members, which we will add in the next step. They also need constraints to dictate their movement behavior. By default, they are assigned NoConstraint()
. For our first joint, which we want to keep fixed in place, we'll give it an AnchorConstraint()
.
We can add joints using the add_joint!(setup, x, y, constraint)
method, which will return the index of the joint that was created. In this case, since we don't have too many joints to add, we can simply assign them to individual variables.
You can look at what you've created at any point by calling the plot_setup(::StaticSetup)
function on your setup! Below we will use this to see what we've created in our example workflow. The use of this function is shown in step 1, but is hidden in future steps.
j1 = add_joint!(my_setup, 0, 0, AnchorConstraint())
j2 = add_joint!(my_setup, 1, 0)
j3 = add_joint!(my_setup, 1, 1)
j4 = add_joint!(my_setup, 2, 0)
j5 = add_joint!(my_setup, 2, 1)
j6 = add_joint!(my_setup, 3, 0)
j7 = add_joint!(my_setup, 3, 1)
j8 = add_joint!(my_setup, 4, 0)
plot_setup(my_setup)
Notice the following
- Joint 1 was given an
AnchorConstraint()
which will keep it fixed in place. - Points were automatically labeled in the order they were created.
- Constraints were depicted in green denoting the kind of motion allowed for the joint.
If we want to modify constraints after the joint was already added, we can do so using the set_constraint!
function. Lets add an XRollerConstraint()
to J8, as we want it to stay fixed on the Y axis, but be allowed to move along the X axis.
set_constraint!(my_setup, j8, XRollerConstraint())
2. Add Members
Now we need to connect our joints via members.
Similar to how a joint 's behavior is controlled by its constraint, a member's behavior is controlled by its material, these are accessed via the Materials
submodule. Members have a default for this as well: A PerfectMaterial()
, which is a very thick material with a tensile strength a few orders higher than that of Tungsten, the strongest rote metal.
Lets use standard 2x4 lumber for our truss structure.
m = Materials.Pine2x4()
StaticMaterial{Float64, Float64, Float64, Float64}(0.00338709, 4.141e9, 8.0e7, 0.8778118)
We can now add members using the add_member!(setup, j1, j2, material)
function. This function will also return an identifier for each member that could be used to refer to them later on, but since we don't need to do anything directly to them after this, we don't need to assign them to anything.
Lets set up a more proper-looking truss.
add_member!(my_setup, j1, j2, m)
add_member!(my_setup, j1, j3, m)
add_member!(my_setup, j2, j3, m)
add_member!(my_setup, j2, j4, m)
add_member!(my_setup, j3, j4, m)
add_member!(my_setup, j3, j5, m)
add_member!(my_setup, j4, j5, m)
add_member!(my_setup, j4, j6, m)
add_member!(my_setup, j5, j6, m)
add_member!(my_setup, j5, j7, m)
add_member!(my_setup, j6, j7, m)
add_member!(my_setup, j6, j8, m)
add_member!(my_setup, j7, j8, m)
Whew! That was a lot of members. This can be tedious, but it's a necessary step in creating our masterpiece. Looking at the result immediately shows us the fruits of our labor, as we now have a very nice looking structure.
3. Add Forces
This truss isn't very useful to us unless we understand more about how it handles loads!
We can add forces to joints by using the set_force!(setup, j, fx, fy)
function. Lets add a $50lbf$ ($222.411N$) load directly on the top of the truss, which would be at J5 in the -Y direction.
set_force!(my_setup, j5, 0, -222.411)
Now we can see a very nice pink arrow depicting our force and how it's acting on our structure!
4. Calculate Displacements
After completing step 3, we've fully defined a statics problem. Now the question is: What can we actually do with it? The first question is generally centered around how the forces acting on the truss deforms it, which we can easily calculate using the solve_displacements(setup)
function!
The displacements we get out of this funciton will follow the same order as the joints we created, so we can see the displacement that happened to J5, we simply access it in the calculated displacements using j5
. We can also access each component of the displacements via displacement[idx].x
or -.y
if needed.
displacements = solve_displacements(my_setup)
displacements[j5]
SimpleStatics.Vector2D{Float64}(1.5857119374224258e-5, -0.00010035062436817928)
It looks like J5 moved down by about $3.4\times10^{-5}m$ or $0.00137"$. This certainly isn't a lot. Let see what happened visually as well to confirm this for all of our points!
This time our plot_setup function will need to have the displacements
data as well to be able to show us what actually happened.
plot_setup(my_setup, displacements=displacements)
Members are now displayed in green to show us the displaced structure, and sure enough, none of the points really moved. Lets try adding a few thousand more pounds to J5 and seeing what happens.
set_force!(my_setup, j5, 0, -22241.11) # about 5000 pounds force.
displacements = solve_displacements(my_setup)
displacements[j5]
SimpleStatics.Vector2D{Float64}(0.0015857126503871337, -0.010035066948763124)
Hmm... we're applying $5k lbf$ and only seeing about $0.4"$ of sag? That doesn't sound right. Surely 2x4 lumber isn't that strong? There's good news and bad news here.
- Good news: The 2x4 lumber you built your truss with is absolutely that strong.
- Bad news: The screws you used to hold them together were not.
5. Calculate Member Forces
Okay, so how do we solve this? Let's say a standard deck screw can hold $100lbf$ ($444.822N$) comfortably before it shears, and we use two screws per board, getting about $850N$ (conservatively) of force that each board (member) can handle before it breaks.
We can calculate the force each board is under with the solve_member_forces(setup, displacements)
function.
We can also pass the result of solve_member_forces
into our plot_setup
function using the member_forces
keyword! Though these will only be visible if displacements
are also present.
member_forces = solve_member_forces(my_setup, displacements)
maximum(member_forces), minimum(member_forces) # in Newtons
(22241.110000000022, -22241.110000000015)
First and foremost we notice that, as expected, our truss will be destroyed ten times over given the maximum force on the members.
Conventionally, negative forces indicate tension and positive forces indicate compression. This signage is arbitrary, but it's what was chosen for this library.
Lets switch back to our $50lbf$ setup and check the maximum member force to see if we can at least bear that load without the risk of failure.
set_force!(my_setup, j5, 0, -222.411) # about 50 pounds force.
displacements = solve_displacements(my_setup)
member_forces = solve_member_forces(my_setup, displacements)
13-element Vector{Float64}:
-111.20550000000006
157.26832631048157
0.0
-111.20550000000004
-157.2683263104815
222.4110000000004
111.20550000000009
-222.4110000000002
157.26832631048137
111.20550000000021
-0.0
-111.20550000000021
157.26832631048157
Now we have some interesting results! Lets go over them, noting that compressive forces are shown in red and tensile forces in blue.
- Notice that M3 and M11 are so-called zero force members. These members technically don't bear any load, but are necessary to constrain the system of equations that our setup has generated.
- M6 and M8 have equal and opposite forces equal to the load on J5, whereas M9 and M10, as well as M4 and M5 are much lower, due to the load being distributed (unevenly) between the two.
What will happen if we modify our truss slightly by recreating it with a member from J4 to J7 rather than J5 to J6.
my_setup = StaticSetup()
j1 = add_joint!(my_setup, 0, 0, AnchorConstraint())
# ...
set_force!(my_setup, j5, 0, -222.411)
displacements = solve_displacements(my_setup)
member_forces = solve_member_forces(my_setup, displacements)
13-element Vector{Float64}:
-111.20550000000007
157.26832631048157
0.0
-111.20550000000009
-157.2683263104813
222.41100000000034
222.41100000000006
-111.2055000000001
-157.2683263104813
222.41100000000023
-0.0
-111.20550000000014
157.2683263104815
Apparently nothing good, as now one extra member is under a significantly higher load. What if we add a few extra cross members?
add_member!(my_setup, j2, j5, m)
add_member!(my_setup, j5, j6, m)
displacements = solve_displacements(my_setup)
member_forces = solve_member_forces(my_setup, displacements)
maximum(member_forces), minimum(member_forces)
(161.59069248271976, -172.0258075172804)
The result is a bit messy, but it looks like the maximum member force was reduced!
6. Calculate Reaction Forces
We can see how these forces are affecting our constraints by using the solve_reaction_forces(setup, displacements)
function, then passing in the result to our plot_setup
function with the reactions
keyword.
reaction_forces = solve_reaction_forces(my_setup, displacements)
plot_setup(my_setup, displacements=displacements, member_forces=member_forces, reactions=reaction_forces)